﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Renci.iRods.Packets;
using Renci.iRods.Exceptions;

namespace Renci.iRods.Common
{
    internal class Message : Message<Packet>
    {
        public Message(MessageTypes messageType)
            : base(messageType, RequestCodes.None, null)
        {
        }

        public Message(MessageTypes messageType, RequestCodes requestCode)
            : base(messageType, requestCode, null)
        {
        }

        public Message(MessageTypes messageType, RequestCodes requestCode, Packet packet)
            : base(messageType, requestCode, packet)
        {
        }

        public Message(MessageTypes messageType, RequestCodes requestCode, Packet packet, Stream binaryData)
            : base(messageType, requestCode, packet, binaryData)
        {
        }
    }

    internal class Message<T> where T: Packet
    {
        /// <summary>
        /// Gets the type of the message.
        /// </summary>
        /// <value>
        /// The type of the message.
        /// </value>
        public MessageTypes MessageType { get; private set; }

        public RequestCodes RequestCode { get; private set; }

        public ErrorCodes ErrorCode { get; private set; }

        public T Body { get; private set; }

        public IRodsException Exception { get; private set; }

        public Stream BinaryData { get; private set; }

        public Message(MessageTypes messageType, RequestCodes requestCode, T body)
        {
            this.MessageType = messageType;
            this.RequestCode = requestCode;
            this.Body = body;
        }

        public Message(MessageTypes messageType, RequestCodes requestCode, T body, Stream binaryData)
            : this(messageType, requestCode, body)
        {
            this.BinaryData = binaryData;
        }


        public Message(MessageHeaderPacket header, T body, ErrorPacket errorPacket, Stream binaryData)
        {
            this.MessageType = header.MessageType;
            if (header.InformationNumber > 0)
            {
                this.RequestCode = (RequestCodes)header.InformationNumber;
            }
            else if (header.InformationNumber < 0)
            {
                this.ErrorCode = (ErrorCodes)header.InformationNumber;
            }

            this.Body = body;


            if (this.ErrorCode != ErrorCodes.None)
            {
                IRodsException currentException = null;

                if (errorPacket != null && errorPacket.Count > 0)
                {
                    foreach (var error in errorPacket.Messages)
                    {
                        if (currentException == null)
                        {
                            currentException = new IRodsException(error.Message);
                        }
                        else
                        {
                            currentException = new IRodsException(error.Message, currentException);
                        }
                    }
                }
                this.Exception = new IRodsException(this.ErrorCode, currentException);
            }


            this.BinaryData = binaryData;
        }

        public byte[] GetBytes(PackingType packingType)
        {
            byte[] bodyData = null;
            var bodyLength = 0;
            switch (packingType)
            {
                case PackingType.Native:
                    if (this.Body != null)
                    {
                        bodyData = this.Body.PackAsBinary().ToArray();
                        bodyLength = bodyData.Length;
                    }
                    break;
                case PackingType.Xml:
                    if (this.Body != null)
                    {
                        bodyData = this.Body.PackAsXml().ToArray();
                        bodyLength = bodyData.Length;
                    }
                    break;
                default:
                    break;
            }

            //  Build header
            //  TODO:   See if we need to send error code
            var binaryLength = (this.BinaryData == null) ? 0 : this.BinaryData.Length;

            //  TODO:   Add conversion protection from long to int
            var header = new MessageHeaderPacket(this.MessageType, bodyLength, 0, (int)binaryLength, (int)this.RequestCode);

            var headerData = header.PackAsXml().ToArray();

            using (var ms = new MemoryStream())
            {
                var lengthData = BitConverter.GetBytes((uint)headerData.Length).Reverse().ToArray();
                ms.Write(lengthData, 0, lengthData.Length);
                ms.Write(headerData, 0, headerData.Length);
                if (bodyData != null)
                    ms.Write(bodyData, 0, bodyData.Length);
                //  TODO:   Write error messages information
                if (this.BinaryData != null)
                {
                    //  TODO:   Read data binary on the fly so we don't have to store file bytes in memory
                    var buffer = new byte[this.BinaryData.Length];
                    this.BinaryData.Read(buffer, 0, buffer.Length);
                    ms.Write(buffer, 0, buffer.Length);
                }
                return ms.ToArray();
            }
        }
    }
}
